﻿/********************************************************************************
* Copyright 2010 Zane Thorn (zane.thorn@gmail.com)                              *
*                                                                               *
* NeturalMath is free software: you can redistribute it and/or modify           *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation, either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* NeturalMath is distributed in the hope that it will be useful,                *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with NeturalMath.  If not, see <http://www.gnu.org/licenses/>.          *
********************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NeturalMath.Properties;

namespace NeturalMath.Units
{
    /// <summary>
    /// Represents a single dimension of measure, such as meters, kilograms, etc.
    /// </summary>
    public class UnitDimension
    {

        #region Constructors

        /// <summary>
        /// Creates a new instance of unit dimension
        /// </summary>
        /// <param name="name"></param>
        /// <param name="type"></param>
        /// <param name="symbol"></param>
        /// <param name="order"></param>
        /// <param name="conversionFromBase"></param>
        private UnitDimension(string name,MeasurementTypes type,string symbol,int order,Func<double,double> conversionFromBase,Func<double,double> conversionToBase)
        {
            Name = name;
            Type = type;
            Symbol = symbol;
            ConversionFromBase = conversionFromBase;
            ConversionToBase = conversionToBase;
            Order = order;
        }

        /// <summary>
        /// Creates a new kind of unit dimension (e.g. meters, pounds, etc.)
        /// </summary>
        /// <param name="name"></param>
        /// <param name="type"></param>
        /// <param name="symbol"></param>
        /// <param name="conversionFromBase"></param>
        public UnitDimension(string name, MeasurementTypes type, string symbol, Func<double, double> conversionFromBase, Func<double, double> conversionToBase)
            :this(name,type,symbol,1,conversionFromBase,conversionToBase)
        {
            
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the friendly name for the unit
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Gets the measurement type for the unit
        /// </summary>
        public MeasurementTypes Type { get; private set; }

        /// <summary>
        /// Gets the commonly used symbol for the dimension
        /// </summary>
        public string Symbol { get; private set; }

        /// <summary>
        /// Gets the power of the dimension (ex. m^2)
        /// </summary>
        public int Order { get; private set; }

        /// <summary>
        /// Converts from the base unit type to this unit type
        /// </summary>
        public Func<double,double> ConversionFromBase { get; private set; }

        public Func<double, double> ConversionToBase { get; private set; }

        #endregion

       

        #region Operators

        /// <summary>
        /// Multiplies dimensions to create a new dimension
        /// </summary>
        /// <param name="u1"></param>
        /// <param name="u2"></param>
        /// <returns></returns>
        public static UnitDimension operator *(UnitDimension u1,UnitDimension u2)
        {
            if (u1.Type != u2.Type)
                throw new UnitConversionException(ErrorCodes.UnitDimensionMismatch, Resources.UnitDimensionMismatch);

            var totalOrder = u1.Order + u2.Order;
            if (totalOrder == 0)
                return null;

            Func<double,double> fromConversion = (value) => u1.ConversionFromBase(u2.ConversionFromBase(value)) ;
            Func<double, double> toConversion = (value) => u1.ConversionToBase(u2.ConversionToBase(value)); 

            return new UnitDimension(u1.Name, u1.Type, u1.Symbol, totalOrder, fromConversion,toConversion);
        }

        /// <summary>
        /// Divides dimensions to create a new dimension
        /// </summary>
        /// <param name="u1"></param>
        /// <param name="u2"></param>
        /// <returns></returns>
        public static UnitDimension operator /(UnitDimension u1, UnitDimension u2)
        {
            if (u1.Type != u2.Type)
                throw new UnitConversionException(ErrorCodes.UnitDimensionMismatch, Resources.UnitDimensionMismatch);

            var totalOrder = u1.Order - u2.Order;
            if (totalOrder == 0)
                return null;

            Func<double, double> fromConversion = (value) => u1.ConversionFromBase(u2.ConversionToBase(value));
            Func<double, double> toConversion = (value) => u1.ConversionToBase(u2.ConversionFromBase(value));

            return new UnitDimension(u1.Name, u1.Type, u1.Symbol, totalOrder, fromConversion, toConversion);
        }

        #endregion

        #region Built in Dimensions

        #region Baseline Dimensions (SI)

        /// <summary>
        /// Represents measurements in meters
        /// </summary>
        public static readonly UnitDimension Meter = new UnitDimension("Meters", MeasurementTypes.Length, "m", (value) => value, (value) => value);

        /// <summary>
        /// Represents measurements in grams
        /// </summary>
        public static readonly UnitDimension Grams = new UnitDimension("Grams", MeasurementTypes.Mass, "g", (value) => value, (value) => value);

        /// <summary>
        /// Represents measurements in celcius
        /// </summary>
        public static readonly UnitDimension Celcius = new UnitDimension("Celcius", MeasurementTypes.Temperature, "°C", (value) => value, (value) => value);

        /// <summary>
        /// Represents measurements in seconds
        /// </summary>
        public static readonly UnitDimension Seconds = new UnitDimension("Seconds", MeasurementTypes.Time, "s", (value) => value, (value) => value);

        #endregion

        #region SI Lengths

        public static readonly UnitDimension Centimeters = new UnitDimension("Centimeters", MeasurementTypes.Length,
                                                                             "cm", (value) => value * 100, (value) => value /100);

        #endregion

        #region Temperature

        public static readonly UnitDimension Kelvin = new UnitDimension("Kelvin", MeasurementTypes.Length,
                                                                             "°K", (value) => value + 273.15,(value)=>value-273.15);

        public static readonly UnitDimension Farenheit = new UnitDimension("Farenheit", MeasurementTypes.Length,
                                                                             "°F", (value) => value * (9 / 5) + 32, (value) => (value-32)*(5/9));

        #endregion

        #region Time

        public static readonly UnitDimension Minutes = new UnitDimension("Minutes", MeasurementTypes.Length,
                                                                             "min", (value) => value / 60, (value) => value * 60);

        public static readonly UnitDimension Hours = new UnitDimension("Hours", MeasurementTypes.Length,
                                                                             "hr", (value) => value / 3600, (value) => value * 3600);

        public static readonly UnitDimension Days = new UnitDimension("Days", MeasurementTypes.Length,
                                                                             "day", (value) => value / 86400, (value) => value* 86400);

        #endregion

        #endregion

    }
}
